package sdkservices

import (
	"context"
	"io/fs"
	"time"

	"go.autokitteh.dev/autokitteh/sdk/sdkbuildfile"
	"go.autokitteh.dev/autokitteh/sdk/sdkexecutor"
	"go.autokitteh.dev/autokitteh/sdk/sdktypes"
)

type Runtimes interface {
	List(ctx context.Context) ([]sdktypes.Runtime, error)
	New(ctx context.Context, name sdktypes.Symbol) (Runtime, error)

	Build(ctx context.Context, fs fs.FS, symbols []sdktypes.Symbol, memo map[string]string) (*sdkbuildfile.BuildFile, error)

	Run(
		ctx context.Context,
		rid sdktypes.RunID,
		sid sdktypes.SessionID,
		path string,
		build *sdkbuildfile.BuildFile,
		globals map[string]sdktypes.Value,
		durable bool,
		cbs *RunCallbacks,
	) (Run, error)
}

type Runtime interface {
	Get() sdktypes.Runtime

	// Returns sdktypes.ProgramErrorAsError if not internal error.
	Build(ctx context.Context, fs fs.FS, path string, symbols []sdktypes.Symbol) (sdktypes.BuildArtifact, error)

	// Returns sdktypes.ProgramErrorAsError if not internal error.
	Run(
		ctx context.Context,
		rid sdktypes.RunID, // generated by caller. guaranteed to be unique system-wide.
		sid sdktypes.SessionID,
		path string, // where to start running from.
		compiled map[string][]byte,
		values map[string]sdktypes.Value,
		durable bool,
		cbs *RunCallbacks,
	) (Run, error)
}

// TODO: This should be an interface.
type RunCallbacks struct {
	// Returns sdktypes.ProgramErrorAsError if not internal error.
	Load func(ctx context.Context, rid sdktypes.RunID, path string) (map[string]sdktypes.Value, error)
	// Returns sdktypes.ProgramErrorAsError if not internal error.
	Call func(ctx context.Context, rid sdktypes.RunID, v sdktypes.Value, args []sdktypes.Value, kwargs map[string]sdktypes.Value) (sdktypes.Value, error)

	IsDeploymentActive func(ctx context.Context) (bool, error)
	NewRunID           func() (sdktypes.RunID, error)
	Now                func(ctx context.Context, rid sdktypes.RunID) (time.Time, error)
	Print              func(ctx context.Context, rid sdktypes.RunID, text string) error
	Sleep              func(ctx context.Context, rid sdktypes.RunID, d time.Duration) error
	Start              func(ctx context.Context, rid sdktypes.RunID, project sdktypes.Symbol, loc sdktypes.CodeLocation, inputs map[string]sdktypes.Value, memo map[string]string) (sdktypes.SessionID, error)

	// Events
	Subscribe   func(ctx context.Context, rid sdktypes.RunID, name, filter string) (string, error)
	Unsubscribe func(ctx context.Context, rid sdktypes.RunID, signalID string) error
	NextEvent   func(ctx context.Context, rid sdktypes.RunID, signalIDs []string, timeout time.Duration) (sdktypes.Value, error)

	// Signals
	Signal     func(ctx context.Context, rid sdktypes.RunID, sid sdktypes.SessionID, name string, payload sdktypes.Value) error
	NextSignal func(ctx context.Context, rid sdktypes.RunID, names []string, timeout time.Duration) (*RunSignal, error)

	// Store
	ListStoreValues   func(ctx context.Context, rid sdktypes.RunID) ([]string, error)
	MutateStoreValue  func(ctx context.Context, rid sdktypes.RunID, key, op string, operands ...sdktypes.Value) (sdktypes.Value, error)
	PublishStoreValue func(ctx context.Context, rid sdktypes.RunID, key string) error

	// Outcome
	Outcome func(ctx context.Context, rid sdktypes.RunID, v sdktypes.Value) error
}

type Run interface {
	ID() sdktypes.RunID

	Close()

	sdkexecutor.Executor
}

type RunSignal struct {
	Name    string         `json:"name"`
	Payload sdktypes.Value `json:"payload"`
}
